﻿using System;
using System.IO;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using ThePresenter.Net.PresenterCom.Actions;
using ThePresenter.Properties;


namespace ThePresenter.Net.NetCom
{
	/// <summary>
	/// Delegate for callback function
	/// </summary>
	public delegate void NetComClientConnectedEventHandler(ServerInfo sender, bool Successful);

	public class NetComClient
	{
		// HACK: Debug class
		private DebugClass _D;

		/// <summary>
		/// IncomingAction event handler delegate 
		/// </summary>
		/// <param name="sender">server that sent action</param>
		/// <param name="action">action received</param>
		public delegate void IncomingServerActionHandler(ServerInfo sender, PresenterAction action);
		public event IncomingServerActionHandler IncomingAction;

		/// <summary>
		/// Occurs when server disconnects.
		/// </summary>
		public event Client.ClientDisconnectedEventHandler ServerDisconnected;

		private ServerInfo _ConnectedServer;
		private Client _Server;

		public bool Connected
		{
			get {
				if (this._Server != null)
					return this._Server.IsConnected;
				else
					return false;
			}
		}

		public NetComClient()
		{
			_D = new DebugClass(Resources.Client);
		}

		public void Connect(ServerInfo ServerInfo, NetComClientConnectedEventHandler Callback)
		{
			if (this.Connected)
				this.Disconnect();

			if (ServerInfo == null) throw new ArgumentException("ServerInfo cannot be null.");

			Socket S = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
			// S.Connect(ServerInfo.ServerEndPoint); // TODO: osetrit, ked sa nepodari pripojit, asi na vyssich vrstvach

			ClientConnectState State = new ClientConnectState();
			State.ServerInfo = ServerInfo;
			State.ClientSocket = S;
			State.Callback = Callback;
				
			 S.BeginConnect(ServerInfo.ServerEndPoint, ServerConnectedCallback, State);
			// S.BeginConnect(IPAddress.Parse("127.0.0.1"), 100, ServerConnectedCallback, State);
		}

		private void ServerConnectedCallback(IAsyncResult result)
		{
			ClientConnectState State = (ClientConnectState)result.AsyncState;

			try
			{
				State.ClientSocket.EndConnect(result);
			}
			catch (SocketException)
			{
				if (State.Callback != null) State.Callback(State.ServerInfo, false);
				return;
			}

			this._ConnectedServer = State.ServerInfo;

			this._Server = new Client(State.ClientSocket);
			this._Server.OnDisconnected += new Client.ClientDisconnectedEventHandler(_Server_OnDisconnected);
			this._Server.IncomingData += new Client.ClientIncomingDataHandler(Server_IncomingData);
			this._Server.Start();

			if (State.Callback != null) State.Callback(State.ServerInfo, true);

			_D.Write(ThePresenter.Properties.Resources.WaitingForDataFrom, State.ClientSocket.RemoteEndPoint.ToString());
		}

		void _Server_OnDisconnected(PresenterCom.Uids.Uid sender)
		{
			if (this.ServerDisconnected != null) this.ServerDisconnected(sender);
		}

		void Server_IncomingData(object sender, byte[] Data)
		{
			ServerInfo Sender = sender as ServerInfo;
			PresenterAction action;

			using (MemoryStream MS = new MemoryStream(Data))
			{
				BinaryFormatter BF = new BinaryFormatter();
				action = (PresenterAction)BF.Deserialize(MS);
			}

			if (IncomingAction != null)
				IncomingAction(Sender, action);
		}

		public void SendData(byte[] Data)
		{
			_Server.Send(Data);
		}

		public void Disconnect()
		{
			this._Server.Disconnect();
			this._ConnectedServer = null;
		}

		public void Dispose()
		{
			this.Disconnect();
		}
	}

	class ClientConnectState
	{
		internal ServerInfo ServerInfo;
		internal Socket ClientSocket;
		internal NetComClientConnectedEventHandler Callback;
	}
}
